package inno.treeRepro;

import inno.data.StockFullData;
import inno.specificOperator.Addition;
import inno.specificOperator.And;
import inno.specificOperator.BiggerThan;
import inno.specificOperator.MovingAvgOperator;
import inno.specificOperator.Multiply;
import inno.specificOperator.Operator;
import inno.specificOperator.Or;
import inno.specificOperator.Power;
import inno.specificOperator.Subtraction;
import inno.specificOperator.Xor;
import inno.specificValue.ClosingPrice;
import inno.specificValue.HighPrice;
import inno.specificValue.LowPrice;
import inno.specificValue.OpenPrice;
import inno.specificValue.SimpleConst;
import inno.specificValue.SimpleConstBump;
import inno.specificValue.Value;

import java.util.ArrayList;
import java.util.HashMap;

public class Chromosome implements Comparable<Chromosome>{
	
	private Double myFitness;
	private Node root;	
	private HashMap<Integer, ArrayList<Node>> treeMap = new HashMap<Integer, ArrayList<Node>>(); // this is a  hash map of the tree far fast approch where each key is the level and the values
	private HashMap<Integer, ArrayList<Node>> dfsMap = new HashMap<Integer, ArrayList<Node>>(); // this is a  hash map of the tree far fast approch where each key is the level and the values
	private HashMap<Integer, ArrayList<Node>> treeLevels = new HashMap<Integer, ArrayList<Node>>(); // this is a  hash map of the tree far fast approch where each key is the level and the values
	private int numOfLevels = 5;			// are the nodes in that level ordered from left to right
	
	private String operators [] = // all the options to choose from
		{
			"Addition","And","BiggerThan","MovingAvgOperator","Multiply","Operator","Or","Subtraction","Xor"
			
		};
	private String values [] = {"ClosingPrice","HighPrice","LowPrice","OpenPrice","SimpleConst"};
	private String stockFigure;
	private StockFullData sfd; // change this to get from a singelton
	
	// constructor
	public Chromosome(String stockFigure) {
		this.stockFigure = stockFigure;
		root=intilizeChromosome(numOfLevels);
		dfs(root,0,0); // do dfs over tree
	 //   System.out.println("Out of total: "+dfsMap.size());		
	}
	// the C.Constructor
	public Chromosome(Chromosome other){ 
		this.stockFigure = new String(other.getStockFigure());
		this.root =new Node(other.getRoot());
		dfs(root,0,0);
	}
	// scheme constructor
	public Chromosome(String name, Node root){
		this.root = root;
		this.stockFigure = new String(name);
	}
	
	
	public HashMap<Integer, ArrayList<Node>> getDfsMap() {
		return dfsMap;
	}
	public void setDfsMap(HashMap<Integer, ArrayList<Node>> dfsMap) {
		this.dfsMap = dfsMap;
	}
	
	public Double getPredictedVal(int bump,int day){
		  return calculate(root,bump);
	}
	
	private Double calculate(Node pos, int bump){
		// if it's a leaf
		if (true==pos.isLeaf()){
			Double val = pos.getValue(bump);;
			return val;
		}
		// if it's a single son node
		else if (true==pos.isSingleNoded()){
			Double val = pos.getValue(calculate(pos.getLeftSon(),bump).intValue());
			return val;
		}
		// else, binary node sons
		else{
			String tag= pos.getOperator().getTag();
			Double val1 = calculate(pos.getLeftSon(),bump);
			Double val2 = calculate(pos.getLRightSon(),bump);
			if (tag.equals("MAVG")==true)
			{
				Double valFinal =  pos.getOperator().calculate(val1,val2);
			}
			Double valFinal =  pos.getOperator().calculate(val1,val2);
					
			return valFinal;
		}
	}
	
	private Node intilizeChromosome(int level)
	{
		if (level==0){ // we got to level 0 and we need to assign some consts as params
			Node tempNode;
			double chooseConst = Math.random();
			if (chooseConst>0.4){
				tempNode = new Node(new SimpleConst(Math.random()*100),null); 
			}
			else{
				tempNode = new Node(new SimpleConstBump(),null);
			}
				
   		 	return tempNode; 
   		 	
		}
		else if (level==1) // then we need to assign a leaf
		{
			 int randValue = (int) Math.round(Math.random()*4); // choose a random value
			 boolean isConst=false;
			 Node tempNode =null;

			 switch (randValue) { // get the random node
		        case 0:  tempNode = new Node(new ClosingPrice(stockFigure),null); 
             break;
		        case 1:  tempNode = new Node(new HighPrice(stockFigure), null); 
             break;
		        case 2:  tempNode = new Node(new LowPrice(stockFigure),null); 
             break;
		        case 3:  tempNode = new Node(new OpenPrice(stockFigure),null); 
             break;
		        case 4:  tempNode = new Node(new SimpleConstBump(),null); // choose a random num from one to 100 
		        		 isConst=true;
		        case 5:  tempNode = new Node(new SimpleConst(Math.random()*100),null); // choose a random num from one to 100 
       		 			 isConst=true;
		        		 
             break;}
			// setHash(tempNode, level);
			 if (isConst==false) // if it is not const
				 tempNode.setLeftNode(intilizeChromosome(0));
			 return tempNode;
		}
		else
		{
			double rand = Math.random(); 
			if (rand>0.3) // then this is a operator
			{
				int randOpt = (int) Math.round(Math.random()*4); // choose one of the opt
				Node tempNode =null;
		        switch (randOpt) {
			        case 0:  tempNode = new Node(null, new Addition()); 
	                break;
			        case 1:  tempNode = new Node(null, new MovingAvgOperator(stockFigure)); 
	                break;
			        case 2:  tempNode = new Node(null, new Multiply()); 
	                break;
			        case 3:  tempNode = new Node(null, new Subtraction()); 
	                break;
			        case 4:  tempNode = new Node(null, new Power()); 
	                break;
	               }
		       
		        tempNode.setLeftNode(intilizeChromosome((level-1)));
		        tempNode.setRightNode(intilizeChromosome((level-1)));
				//setHash(tempNode, level);
		        return tempNode;
			}
			else{ // this is a value
				 int randValue = (int) Math.round(Math.random()*4); // choose a random value
				 Node tempNode = null;

				 switch (randValue) { // get the random node and assign him a param
			        case 0:  tempNode = new Node(new ClosingPrice(stockFigure),null); 
			        		 tempNode.setLeftNode(intilizeChromosome(0)); 
	             break;
			        case 1:  tempNode = new Node(new HighPrice(stockFigure), null); 
	        		 		 tempNode.setLeftNode(intilizeChromosome(0)); 
	             break;
			        case 2:  tempNode = new Node(new LowPrice(stockFigure),null); 
	        		 		 tempNode.setLeftNode(intilizeChromosome(0)); 
	             break;
			        case 3:  tempNode = new Node(new OpenPrice(stockFigure),null); 
	        		 		 tempNode.setLeftNode(intilizeChromosome(0)); 
	             break;
			        case 4:  tempNode = new Node(new SimpleConst(Math.random()*100),null); // choose a random num from one to 100 
	            
	             break;}
				// setHash(tempNode, level);
				 return tempNode; // return the node
				
			}

		}
			
	}
	public HashMap<Integer, ArrayList<Node>> getTreeLevels() {
		return treeLevels;
	}
	public void setTreeLevels(HashMap<Integer, ArrayList<Node>> treeLevels) {
		this.treeLevels = treeLevels;
	}
	public Node getRoot() {
		return root;
	}
	public void setRoot(Node root) {
		this.root = root;
	}
	public HashMap<Integer, ArrayList<Node>> getTreeMap() {
		return treeMap;
	}
	public void setTreeMap(HashMap<Integer, ArrayList<Node>> treeMap) {
		this.treeMap = treeMap;
	}
	public int getNumOfLevels() {
		return numOfLevels;
	}
	public void setNumOfLevels(int numOfLevels) {
		this.numOfLevels = numOfLevels;
	}
	public String[] getOperators() {
		return operators;
	}
	public void setOperators(String[] operators) {
		this.operators = operators;
	}
	public String[] getValues() {
		return values;
	}
	public void setValues(String[] values) {
		this.values = values;
	}
	public StockFullData getSfd() {
		return sfd;
	}
	public void setSfd(StockFullData sfd) {
		this.sfd = sfd;
	}
	public void setMyFitness(Double myFitness) {
		this.myFitness = myFitness;
	}
	public void setStockFigure(String stockFigure) {
		this.stockFigure = stockFigure;
	}
	public double getMyFitness(){
		  return myFitness;
	}
		 
	public void setFitness(Double newFitness){
		  myFitness = newFitness;
	}
		 
	public String getStockFigure(){
		  return stockFigure;
	}
		 
	
	/*private void setHash(Node node,int level){ // set in the hashMap
		if (treeMap.get(level)==null){ // add to the array list
			ArrayList<Node> tempNodeArray = new ArrayList<Node>();
			tempNodeArray.add(node);
			treeMap.put(level, tempNodeArray);
			
		}
		else{
			ArrayList<Node> tempNodeArray = treeMap.get(level);
			tempNodeArray.add(node);
			treeMap.put(level, tempNodeArray);
		}
	}*/
	
	// use this function on the root as level 0 in order to map the levels of the tree structure
	public void dfs(Node node, int level, int counter){
		
		if (10<counter){
			int a=level;
			System.out.println(getSchemeTag(node));
			System.out.println(a+","+counter);
		}
		
		node.setLevel(level);
		if (node.isSingleNoded()==false){ // if it has two nodes
			if (treeLevels.get(level)==null){ // add to the array list
				ArrayList<Node> tempNodeArray = new ArrayList<Node>();
				tempNodeArray.add(node);
				treeLevels.put(level, tempNodeArray);
				
			}
			else{
				ArrayList<Node> tempNodeArray = treeLevels.get(level);
				tempNodeArray.add(node);
				treeLevels.put(level, tempNodeArray);
			}
		}
	//	System.out.println(dfsMap.get(level)+"This is in level: "+level);
		if (dfsMap.get(level)==null){ // add to the array list
			node.setPosInLine(0);
			ArrayList<Node> tempNodeArray = new ArrayList<Node>();
			tempNodeArray.add(node);
			dfsMap.put(level, tempNodeArray);
			
		}
		else{
			ArrayList<Node> tempNodeArray = dfsMap.get(level);
			node.setPosInLine(tempNodeArray.size()); // put in the size of it
			tempNodeArray.add(node);
			dfsMap.put(level, tempNodeArray);
		}
		if (null!=node.getLeftSon()){
			dfs(node.getLeftSon(),level+1,counter+1);
		}
		if (null!=node.getLRightSon()){
			dfs(node.getLRightSon(),level+1,counter+1);
		}
	}
	/*public Node copyTree(Node node){
		Node newNode = new Node(node.getValueOperator(), node.getMyOperator());
		if (node.getLeftSon()!=null)
		{
			newNode.setLeftNode(copyTree(node.getLeftSon()));
		}
		else if (node.getLRightSon()!=null)
		{
			newNode.setRightNode(node.getLRightSon());		
		}
		return newNode;
	}*/
	
	public String toString(){	
		//String ans = stockFigure+"-"+getTag(root,0);
		String ans = stockFigure+"-"+getSchemeTag(root);
		return ans;
	}
	
	// returns the chromosome in a scheme form
	private String getSchemeTag(Node node){
		if (null==node){
			return "";
		}
		return "("+node.getTag()+getSchemeTag(node.getLeftSon())+getSchemeTag(node.getLRightSon())+")";
	}
	
	// between a node and it's successor are 2^nodeLevel+nodePosInLine
	private String getTag(Node node, int nodeIndex){
		
		String ans = nodeIndex + ":" + node.getTag() + ";";
		int shift = 2^node.getLevel()+node.getPosInLine();
		
		if (null!=node.getLeftSon()){
			ans = ans + getTag(node.getLeftSon(),shift);
		}
		else{
			ans = ans + shift + ":NULL;";
		}
		
		if (null!=node.getLRightSon()){
			ans = ans + getTag(node.getLRightSon(),shift+1);
		}
		else{
			ans = ans + (shift+1) + ":NULL;";
		}
		
		return ans;
	}
	
	public Chromosome createChromosomeFromSchemeString(String str){
		
		int nameIndex = str.indexOf("-");
		String name = str.substring(0, nameIndex);
		// jump on the - and ( following it to the end of the phrase (means end pos - last ) sign)
		String scheme = str.substring(nameIndex+2,str.length()-1);
		
		HashMap<String, Operator> operatorsMap = new HashMap<String, Operator>();
		HashMap<String, Value> valuesMap = new HashMap<String, Value>();
		
		operatorsMap.put("AD", new Addition());
		operatorsMap.put("AND", new And());
		operatorsMap.put("BT", new BiggerThan());
		operatorsMap.put("MAVG", new MovingAvgOperator(name));
		operatorsMap.put("MU", new Multiply());
		operatorsMap.put("OR", new Or());
		operatorsMap.put("SU", new Subtraction());
		operatorsMap.put("XOR", new Xor());
		operatorsMap.put("POW", new Power());
		
		valuesMap.put("CP", new ClosingPrice(name));
		valuesMap.put("HP", new HighPrice(name));
		valuesMap.put("LP", new LowPrice(name));
		valuesMap.put("OP", new OpenPrice(name));
		valuesMap.put("BU", new SimpleConstBump());
		
		Node newRoot= schemeTranslate(scheme,operatorsMap,valuesMap);
		return new Chromosome(name, newRoot);
	}
	
	private Node schemeTranslate(String scheme, HashMap<String, Operator> operatorsMap,
			HashMap<String, Value> valuesMap){
		
		Node nodeAns = null;
		
		// search when the node's name ends
		int index1 = scheme.indexOf("(");
		String tag;
		if (0<index1){
			tag = scheme.substring(0, index1);
		}
		else{
			tag = scheme;
		}
		
		if (null!=operatorsMap.get(tag)){
			nodeAns = new Node(null, operatorsMap.get(tag)); 
		}
		else{
			if (null!=valuesMap.get(tag)){
				nodeAns = new Node(valuesMap.get(tag), null);
			}
			// simple const
			else{
				nodeAns = new Node(new SimpleConst(Double.parseDouble(tag)),null);
			}			 
		}
		
		if (true==scheme.contains("(")){
			// search for the first parameter
			//int index2 = scheme.indexOf(")");
			int index2 = getEqualParPos(scheme);
			
			String sonScheme = scheme.substring(index1+1, index2);
			nodeAns.setLeftNode(schemeTranslate(sonScheme,operatorsMap,valuesMap));
			// means two parameters
			if (index2!=(scheme.length()-1)){
				sonScheme = scheme.substring(index2+2,scheme.length()-1);
				nodeAns.setRightNode(schemeTranslate(sonScheme,operatorsMap,valuesMap));
			}
		}
		return nodeAns;
	}
	
	// counts the ( ) sum in order to find scheme values
	private int getEqualParPos(String str){
		char[] strArray = str.toCharArray();
		int i = 0;
		int counter = 0;
		
		/*while (')'!=strArray[i]){
			if ('('==strArray[i]){
				counter++;
			}
			i++;
		}*/
		int size = str.length();
		while (i<size){
			if ('('==strArray[i]){
				counter++;
				break;
			}
			i++;
		}
		
		while (0<counter){
			i++;
			if ('('==strArray[i]){
				counter++;
			}
			else if (')'==strArray[i]){
				counter--;
			}
		}
		
		return i;
	}
	@Override
	public int compareTo(Chromosome arg0) {
		if (myFitness>arg0.getMyFitness())
			return 1;
		else if (myFitness<arg0.getMyFitness())
			return -1;
		return 0;
	}

}
	

